Repository navigation
feat(domains): add forge activation methods - #320
lucasfcosta wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
2 issues found across 9 files
Confidence score: 3/5
- Running
examples/domain_forge.pycan change a live domain’s Return-Path and trigger re-verification, with no cleanup; the laterupdatealso raises once verification completes. Use a disposable domain and clean up the configuration. tests/domain_forge_async_test.pychecks only returned JSON, so incorrect URLs or HTTP verbs in the async methods can go unnoticed. Assert the mock call arguments, including the URL.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="examples/domain_forge.py">
<violation number="1" location="examples/domain_forge.py:8">
P2: This example changes a hardcoded live domain's Return-Path and triggers re-verification, with no cleanup. Running it can alter production sending config. Once the domain verifies, the `update` call raises the documented 422 `return_path_unavailable`, so the script cannot be rerun. Create a throwaway domain as `examples/domains.py` does, or read the ID from an environment variable.</violation>
</file>
<file name="tests/domain_forge_async_test.py">
<violation number="1" location="tests/domain_forge_async_test.py:23">
P3: The async Forge tests only check the returned JSON, so a wrong path or HTTP verb in `enable_async`, `update_async`, or `verify_async` would still pass. Add a `self.mock.call_args.kwargs["url"]` assertion, as the sync `domain_forge_test.py` and other async tests do.</violation>
</file>
Reply with feedback, questions, or to request a fix.
View guided diff | Re-trigger cubic
| if not os.environ["RESEND_API_KEY"]: | ||
| raise EnvironmentError("RESEND_API_KEY is missing") | ||
|
|
||
| domain_id = "d91cd9bd-1176-453e-8fc1-35364d380206" |
There was a problem hiding this comment.
P2: This example changes a hardcoded live domain's Return-Path and triggers re-verification, with no cleanup. Running it can alter production sending config. Once the domain verifies, the update call raises the documented 422 return_path_unavailable, so the script cannot be rerun. Create a throwaway domain as examples/domains.py does, or read the ID from an environment variable.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At examples/domain_forge.py, line 8:
<comment>This example changes a hardcoded live domain's Return-Path and triggers re-verification, with no cleanup. Running it can alter production sending config. Once the domain verifies, the `update` call raises the documented 422 `return_path_unavailable`, so the script cannot be rerun. Create a throwaway domain as `examples/domains.py` does, or read the ID from an environment variable.</comment>
<file context>
@@ -0,0 +1,35 @@
+if not os.environ["RESEND_API_KEY"]:
+ raise EnvironmentError("RESEND_API_KEY is missing")
+
+domain_id = "d91cd9bd-1176-453e-8fc1-35364d380206"
+
+# Enable: activate Forge on an existing domain. Returns the full domain,
</file context>
| } | ||
| domain = await resend.Domains.Forge.enable_async(params) | ||
| assert domain["id"] == "d91cd9bd-1176-453e-8fc1-35364d380206" | ||
| assert domain["forge_status"] == "pending" |
There was a problem hiding this comment.
P3: The async Forge tests only check the returned JSON, so a wrong path or HTTP verb in enable_async, update_async, or verify_async would still pass. Add a self.mock.call_args.kwargs["url"] assertion, as the sync domain_forge_test.py and other async tests do.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At tests/domain_forge_async_test.py, line 23:
<comment>The async Forge tests only check the returned JSON, so a wrong path or HTTP verb in `enable_async`, `update_async`, or `verify_async` would still pass. Add a `self.mock.call_args.kwargs["url"]` assertion, as the sync `domain_forge_test.py` and other async tests do.</comment>
<file context>
@@ -0,0 +1,87 @@
+ }
+ domain = await resend.Domains.Forge.enable_async(params)
+ assert domain["id"] == "d91cd9bd-1176-453e-8fc1-35364d380206"
+ assert domain["forge_status"] == "pending"
+
+ async def test_domain_forge_enable_async_without_return_path(self) -> None:
</file context>
There was a problem hiding this comment.
0 issues found across 1 file (changes from recent commits).
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Requires human review: Auto-approval blocked by 2 unresolved issues from previous reviews.
View guided diff | Re-trigger cubic
There was a problem hiding this comment.
0 issues found across 7 files (changes from recent commits).
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Requires human review: Auto-approval blocked by 2 unresolved issues from previous reviews.
View guided diff | Re-trigger cubic
1cbc387 to
240fdd5
Compare
There was a problem hiding this comment.
0 issues found across 7 files (changes from recent commits).
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Requires human review: Auto-approval blocked by 2 unresolved issues from previous reviews.
View guided diff | Re-trigger cubic
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
240fdd5 to
c8a396f
Compare
Matches the reshaped API: Domains.Forge keeps only enable (return_path optional), Domains.verify gains an optional scope of ["forge"] that re-checks only the Forge record, and Domains.update gains forge_return_path to move the Forge return path without starting verification. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
1 existing issue remains and 2 new issues found across 9 files (changes from recent commits).
Confidence score: 4/5
- In
resend/domains/_domains.py, these domain-management methods won't work with sending-only API keys. Confirm production callers use full-access keys. - In
resend/domains/_domains.py, the Forge return-path docstring says “another host,” which could lead callers to pass a full hostname instead of a subdomain label. Update it to say “subdomain.” - In
tests/domains_async_test.py, the test could pass even ifverify_asyncposts to the wrong URL. Add the URL assertion used by the sync counterpart.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="resend/domains/_domains.py">
<violation number="1" location="resend/domains/_domains.py:21">
P2: Custom agent: **API Key Permission Check SDK Methods**
Please confirm that production callers use full-access API keys for these new domain-management operations; sending-only keys cannot access non-email endpoints.</violation>
<violation number="2" location="resend/domains/_domains.py:156">
P3: This docstring says the Forge return path moves "to another host", but the value is a subdomain label, as `Domains.Forge.enable` documents for `return_path`. Say "subdomain" so callers don't pass a full hostname.</violation>
</file>
Requires human review: Auto-approval skipped because cubic reviewed only this push, not the earlier force-push. Comment @cubic review to review the whole pull request.
View guided diff | Re-trigger cubic
|
|
||
| TlsOptions = Literal["enforced", "opportunistic"] | ||
|
|
||
| VerifyDomainScope = Literal["forge"] |
There was a problem hiding this comment.
P2: Custom agent: API Key Permission Check SDK Methods
Please confirm that production callers use full-access API keys for these new domain-management operations; sending-only keys cannot access non-email endpoints.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At resend/domains/_domains.py, line 21:
<comment>Please confirm that production callers use full-access API keys for these new domain-management operations; sending-only keys cannot access non-email endpoints.</comment>
<file context>
@@ -18,6 +18,8 @@
TlsOptions = Literal["enforced", "opportunistic"]
+VerifyDomainScope = Literal["forge"]
+
</file context>
| """ | ||
| forge_return_path: NotRequired[str] | ||
| """ | ||
| Move the domain's Forge return path to another host. Only possible |
There was a problem hiding this comment.
P3: This docstring says the Forge return path moves "to another host", but the value is a subdomain label, as Domains.Forge.enable documents for return_path. Say "subdomain" so callers don't pass a full hostname.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At resend/domains/_domains.py, line 156:
<comment>This docstring says the Forge return path moves "to another host", but the value is a subdomain label, as `Domains.Forge.enable` documents for `return_path`. Say "subdomain" so callers don't pass a full hostname.</comment>
<file context>
@@ -149,6 +151,21 @@ class UpdateParams(TypedDict):
"""
+ forge_return_path: NotRequired[str]
+ """
+ Move the domain's Forge return path to another host. Only possible
+ while the Forge record is still verifying. Moving the path does not
+ start verification: fetch the new record with `Domains.get`, add it to
</file context>
| Move the domain's Forge return path to another host. Only possible | |
| Move the domain's Forge return path to another subdomain. Only possible |
Summary
Adds Forge activation to
Domains, matching the folded API surface (the standalone forge verify and update endpoints no longer exist). Ports the Node implementation (resend/resend-node#1193, spec in resend/resend-openapi#144) into this SDK's idioms. Part of DEV-2472.resend.Domains.Forge.enable({ "id": ..., "return_path": ... })→POST /domains/{id}/forge.return_pathis optional; when omitted, the API picks a label that is free in the domain's DNS. Returns the full domain response and is idempotent on the API side. Ships with its_asyncvariant, like the rest of the SDK.resend.Domains.verify(domain_id, params={ "scope": ["forge"] })→POST /domains/{id}/verify.paramsis a new optional argument (Domains.VerifyParams,scope: List[Literal["forge"]]); scoped, only the Forge record is re-checked, so the records that are already verified are not re-proven. Withoutparamsthe request is byte-identical to before. Same onverify_async.resend.Domains.update({ "id": ..., "forge_return_path": ... })→PATCH /domains/{id}.forge_return_pathis a newUpdateParamskey: moves the Forge return path while the Forge record is still verifying, does not start verification, and once the record verifies the API refuses with 422return_path_unavailable. Composes with the other update fields.Domain response types are unchanged: Forge state is read from the Forge CNAME record in the
recordsarray.Validation
flake8 clean, mypy clean on the touched files, pytest: 875 tests passing. Tests assert the wire shapes: enable's empty body, verify's
{"scope": ["forge"]}body (and{}without params), and update'sforge_return_path, sync and async.examples/domain_forge.pynow walks the folded flow: enable, move the path withDomains.update, verify scoped to forge.Notes for reviewers
VerifyParamsis a nested TypedDict onDomainswithscope: NotRequired[List[VerifyDomainScope]], whereVerifyDomainScope = Literal["forge"]lives next toTlsOptions.DomainForgeexposed asDomains.Forge(now enable-only), matching howDomainClaimsis exposed asDomains.Claims.idfrom the request body (it only addresses the path), following theApiKeys.updateprecedent.Summary by cubic
Adds a
Forgesubresource toDomainsand folds Forge verification and return-path moves into the classic domain endpoints, per DEV-2472.Domains.Forge.enableposts to/domains/{id}/forgewith an optionalreturn_path;idis stripped from the request body.Domains.verifynow accepts an optionalscope: ["forge"]to re-check only the Forge record, andDomains.updateacceptsforge_return_pathto move the return path without starting verification.examples/domain_forge.pyexample.Validation
Written for commit 242e609. Summary will update on new commits.
The Forge enable endpoint is live in production; the scoped verify and
forge_return_pathland with the API reshape, which deploys before this ships. Ships with the next resend-python release.